module dactor.scheduler;

private import dactor.coroutine;
private import dactor.log;
private import core.thread;
private import core.stdc.time;

/*
 * Private class, using in Scheduler
 */
class CoroutineList
{
    private Coroutine head;
    private Coroutine tail;

    public bool empty() const {
        return head is null;
    }

    public void pushBack(Coroutine coroutine) {
        coroutine.prev = tail;
        if (tail) tail.next = coroutine;
        tail = coroutine;
        if (!head) head = coroutine;
    }

    public void pushFront(Coroutine coroutine) {
        coroutine.next = head;
        if (head) head.prev = coroutine;
        head = coroutine;
        if (!tail) tail = coroutine;
    }

    public Coroutine popFront() {
        Coroutine coroutine = head;
        if (head) {
            head = head.next;
        }
        if (!head) {
            tail = null;
        }

        coroutine.prev = coroutine.next = null;
        return coroutine;
    }
}

/**
 * Coroutine scheduler.
 */
class Scheduler
{
    private CoroutineList runQueue;
    private Coroutine current;

    private Coroutine[Pid] pid2coroutines;
    private Coroutine[string] name2coroutines;

    private uint counter = 0;

    private this() {
        runQueue = new CoroutineList;
    }

    /**
     * Returns:
     * Current coroutine running in current scheduler.
     */
    public Coroutine getCurrent() {
        return current;
    }

    public bool hasTasks() {
        return !runQueue.empty();
    }

    public Coroutine findByPid(Pid pid) {
        if (Coroutine* c = (pid in pid2coroutines))
            return *c;
        return null;
    }

    public Coroutine findByName(string name) {
        if (Coroutine* c = (name in name2coroutines))
            return *c;
        return null;
    }

    public Pid register(Coroutine c) {
        while (true) {
            uint id = counter ++;
            if (id == 0)
                continue;

            Pid pid = Pid(0, id, time(null));
            if (pid in pid2coroutines)
                continue;

            c.pid = pid;
            pid2coroutines[pid] = c;
            return pid;
        }
    }

    public bool registerName(string name, Coroutine c) {
        if (name in name2coroutines) {
            return false;
        }

        name2coroutines[name] = c;
        c.name = name;
        return true;
    }

    public void unregister(Coroutine c) {
        name2coroutines.remove(c.name);
        pid2coroutines.remove(c.pid);
    }

    /**
     * Put into scheduler's run queue.
     *
     * Params:
     * coroutine = need to schedule
     */
    public void schedule(Coroutine coroutine, bool first=false) {
        assert(!coroutine.prev);
        assert(!coroutine.next);

        if (coroutine.status == Coroutine.Status.SCHEDULING) {
            return;
        }

        coroutine.status = Coroutine.Status.SCHEDULING;
        if (first) {
            runQueue.pushFront(coroutine);
        }
        else {
            runQueue.pushBack(coroutine);
        }
    }

    /**
     * Run scheduler, run all coroutines in queue.
     */
    public void run() {
        while(!runQueue.empty) {
            Coroutine coroutine = runQueue.popFront();
            assert(coroutine);

            coroutine.status = Coroutine.Status.RUNNING;
            current = coroutine;
            //logDebug("begin call coroutine: %p", cast(void*)coroutine);
            coroutine.call();
            if (coroutine.state == Fiber.State.TERM) {
                unregister(coroutine);
            } else if (coroutine.status != Coroutine.Status.WAITING &&
                    coroutine.status != Coroutine.Status.RECEIVING) {
                runQueue.pushBack(coroutine);
            }
            //logDebug("end call coroutine: %p", cast(void*)coroutine);
        }
    }

    /////////////////////////////////////
    private static Scheduler instance_;

    public static this() {
        instance_ = new Scheduler;
    }

    /**
     * Returns:
     * Current scheduler instance.
     */
    public static Scheduler instance() {
        return instance_;
    }
};


